/**
 *
 */
package org.jprobe.junit;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import org.jprobe.reflect.BeanGetterValueInterface;
import org.jprobe.reflect.BeanInspector;
import org.jprobe.reflect.BeanInspectorInterface;
import org.jprobe.reflect.CodeGenerator;
import org.jprobe.reflect.CodeGeneratorInterface;
import org.jprobe.reflect.classpath.AbstractClassPath;

/**
 * JUnitTestCaseGeneratorInterface implementation
 * @author Hussain Al-Mutawa
 * @version 1.0
 */
public class JUnitTestCaseGenerator implements JUnitTestCaseGeneratorInterface {
//	/** classpath of the project **/
//	private transient static final AbstractClassPath CLASSPATH=ClassPathReader.getClassPath();
	/** code generator **/
	private transient static final CodeGeneratorInterface CG = new CodeGenerator();
//	/** the project hosting the bean **/
//	private transient ProjectInterface project;
	/** the class to be tested **/
	private transient Class<?>clazz;
	/** junit test blocks **/
	private transient final SortedSet<JUnitTestUnitBlock<Method>>
		privateMethodsJUnitTestUnitBlocks,
		methodsJUnitTestUnitBlocks;
	private transient final SortedSet<JUnitTestUnitBlock<Constructor<?>>>
		constructorsJUnitTestUnitBlocks;

	public JUnitTestCaseGenerator(){
		super();
		this.privateMethodsJUnitTestUnitBlocks=new TreeSet<JUnitTestUnitBlock<Method>>();
		this.methodsJUnitTestUnitBlocks=new TreeSet<JUnitTestUnitBlock<Method>>();
		this.constructorsJUnitTestUnitBlocks=new TreeSet<JUnitTestUnitBlock<Constructor<?>>>();
	}

	/**
	 * initializes a new junit test generator
	 * @param clazz the class for which test cases are generated
	 */
	public JUnitTestCaseGenerator(final Class<?>clazz) {
		this();
		this.clazz=clazz;
	}

	@Override
	public void generate() throws Exception {
		resetCollections();
		final BeanInspectorInterface bii = new BeanInspector();
		bii.setBean(getTargetClass());
		bii.inspect();
		for(final Method method:bii.getMethods()){
			final JUnitTestUnitBlock<Method> mjtub = new JUnitTestUnitBlock<Method>(method);
			final BeanGetterValueInterface jutub = bii.get(method);
			final String testingBody = getAssertion(method, jutub );
			mjtub.setExpectedExceptionClass(jutub.getExceptionThrown());
			mjtub.setTestingBody(testingBody);
			this.methodsJUnitTestUnitBlocks.add(mjtub);
		}
	}

//	public static String getParametersDeclaration(final Class<?>[]types){
//		final StringBuffer sbuffer = new StringBuffer();
//		sbuffer.append('(');
//		for(final Class<?>type:types){
//			//System.err.println(type);
//			sbuffer.append(CG.getInstantiationCode(type)).append(',');
//		}
//		if(types.length>0){
//			sbuffer.setLength(sbuffer.toString().length()-1);
//		}
//		sbuffer.append(')');
//		return sbuffer.toString();
//	}

	private transient static final List<Class<?>>PRIMITIVES=Arrays.asList(new Class<?>[]{
			int.class	,Integer.class,
			double.class,Double.class,
			float.class	,Float.class,
			char.class	,Character.class,
			long.class	,Long.class,
			short.class	,Short.class,
			byte.class	,Byte.class,
			boolean.class,Boolean.class,
			String.class
	});
	private transient static final List<Class<?>>FLOATS=Arrays.asList(new Class<?>[]{double.class,Double.class,Float.class,float.class});


	@Override
	public String getAssertion(final Method method,final BeanGetterValueInterface actualValue) throws Exception{
		final Object actual = actualValue.getValue();
		final StringBuffer sbuffer = new StringBuffer();
		final Class<?>type=method.getReturnType();
		final String valueType = AbstractClassPath.getClassBinaryName(type);
		final String clsName = AbstractClassPath.getClassBinaryName(getTargetClass());
		sbuffer.append(clsName + " obj = " + CG.getImplementation(getTargetClass())[1] + ";\n");
		final String params=CG.getParametersDeclaration(method.getParameterTypes());
		
		if(Modifier.isPublic(method.getModifiers())){
			sbuffer.append(valueType);
			sbuffer.append(" expected = obj."+method.getName() +'('+ params +')' +";\n");
		}else{
			try{
				boolean bDeclaredMethods = method.getDeclaringClass().getDeclaredMethod(method.getName(), method.getParameterTypes())==method;
				//boolean bMethods = method.getDeclaringClass().getMethod(method.getName(), method.getParameterTypes())==method;
				String types="",declared="";
				if(bDeclaredMethods){
					declared="Declared";
				}
				for(final Class<?>tp:method.getParameterTypes()){
					types += tp.getName()+".class,";
				}
				if(types.endsWith(",")){
					types=types.substring(0,types.length()-1);
				}
				
				sbuffer.append(" java.lang.reflect.Method method = "+method.getDeclaringClass().getName()+".class.get"+declared+"Method(\"\",new Class<?>[]{"+types+"});\n");
				sbuffer.append(" method.setAccessible(true);\n");
				sbuffer.append("Object expected = method.invoke(obj,new Object[]{"+ params +"});\n");
			}catch(final Throwable t){
				sbuffer.append(valueType);
				sbuffer.append("Object expected = null;\n");
			}
		}
		//final Class<?>type = actual.getClass();

		if(actual==null){
			sbuffer.append("org.junit.Assert.assertNull(expected);\n");
		}else if(PRIMITIVES.contains(type)){
			if(type==String.class){
				sbuffer.append(valueType +" actual = \"" + actual + "\";\n");
			}else if(type==char.class ^ type==Character.class){
				sbuffer.append(valueType +" actual = '" + actual + "';\n");
			}else{
				sbuffer.append(valueType +" actual = ("+ valueType + ')' + actual + ";\n");
			}
			if(FLOATS.contains(type)){
				sbuffer.append("org.junit.Assert.assertEquals(expected,actual,0.1);\n");
			}else{
				sbuffer.append("org.junit.Assert.assertEquals(expected,actual);\n");
			}
		}else if(java.util.Collection.class.isAssignableFrom(type) ){
			sbuffer.append("String actual = \"" + Arrays.toString(((Collection<?>)actual).toArray()) + "\";\n");
			sbuffer.append("org.junit.Assert.assertEquals(Arrays.toString(expected.toArray()),actual);\n");
		}else if(type.isArray()){
			sbuffer.append("String actual = \"" + Arrays.toString((Object[])actual) + "\";\n");
			sbuffer.append("org.junit.Assert.assertEquals(Arrays.toString(expected),actual);\n");
		}else{

			//type.getDeclaredMethod("toString", new Class<?>[]{});
			sbuffer.append("String actual = \"" + actual.toString() + "\";\n");
			sbuffer.append("org.junit.Assert.assertEquals(expected.toString(),actual);\n");

			sbuffer.append(getAllGettersAssertions(type, actual));

		}
		return sbuffer.toString();
	}

	@Override
	public String getAllGettersAssertions(final Class<?> bean, final Object obj) throws Exception{
		final StringBuffer sbuffer = new StringBuffer();
		final BeanInspector bi = new BeanInspector();
		bi.setBean(bean);
		bi.setBeanInstance(obj);
		final Method[]mthds=bi.getPublicMethods();
		sbuffer.append("String actualv,expectedv;\n");
		for(final Method mthd:mthds){
			sbuffer.append("actualv = \"" + bi.get(mthd).getValue() + "\"\n" );
			sbuffer.append("expectedv = actual." + mthd.getName() + CG.getParametersDeclaration(mthd.getParameterTypes()) + ";\n");
			sbuffer.append("org.junit.Assert.assertEquals(expectedv,actualv);\n");
		}
		return sbuffer.toString();
	}

	@Override
	public Class<?> getTargetClass() {
		return clazz;
	}

	@Override
	public void setTargetClass(final Class<?> targetClass) {
		clazz=targetClass;
	}

	@SuppressWarnings("unchecked")
	@Override
	public final JUnitTestUnitBlockInterface<? extends Member>[] getJUnitTestUnitBlocks() {
		final SortedSet<JUnitTestUnitBlock<? extends Member>> set = new TreeSet<JUnitTestUnitBlock<? extends Member>>();
		set.addAll(this.privateMethodsJUnitTestUnitBlocks);
		set.addAll(this.methodsJUnitTestUnitBlocks);
		set.addAll(this.constructorsJUnitTestUnitBlocks);
		return set.toArray((JUnitTestUnitBlock<? extends Member>[])Array.newInstance(JUnitTestUnitBlock.class, set.size()));
	}

	@Override
	public final String getTestUnitClassName() {
		return this.clazz.getName().replace('$', '.')+"JProbeTest";
	}

	@SuppressWarnings("unchecked")
	@Override
	public final JUnitTestUnitBlockInterface<Constructor<?>>[] getConstructorsJUnitTestUnitBlocks() {
		return this.constructorsJUnitTestUnitBlocks
		.toArray((JUnitTestUnitBlock<Constructor<?>>[])Array.newInstance(JUnitTestUnitBlock.class, this.constructorsJUnitTestUnitBlocks.size()));

	}

	@SuppressWarnings("unchecked")
	@Override
	public final JUnitTestUnitBlockInterface<Method>[] getMethodsJUnitTestUnitBlocks() {
		return this.methodsJUnitTestUnitBlocks
		.toArray((JUnitTestUnitBlock<Method>[])Array.newInstance(JUnitTestUnitBlock.class, this.methodsJUnitTestUnitBlocks.size()));

	}

	@SuppressWarnings("unchecked")
	@Override
	public final JUnitTestUnitBlockInterface<Method>[] getPrivateMethodsJUnitTestUnitBlocks() {
		return this.privateMethodsJUnitTestUnitBlocks
		.toArray((JUnitTestUnitBlock<Method>[])Array.newInstance(JUnitTestUnitBlock.class, this.privateMethodsJUnitTestUnitBlocks.size()));
	}

	@Override
	public final void resetCollections(){
		this.privateMethodsJUnitTestUnitBlocks.clear();
		this.methodsJUnitTestUnitBlocks.clear();
		this.constructorsJUnitTestUnitBlocks.clear();
	}
}
